/*
 * Copyright (C) Hisilicon Technologies Co., Ltd. 2012-2019. All rights reserved.
 * Description: Function of isp_csc.c
 * Author: ISP SW
 * Create: 2012/06/28
 */

#include "isp_alg.h"
#include "isp_config.h"
#include "isp_ext_config.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* end of #ifdef __cplusplus */

#define ISP_CSC_DFT_LUMA 50
#define ISP_CSC_DFT_CON  50
#define ISP_CSC_DFT_HUE  50
#define ISP_CSC_DFT_SAT  50
#define ISP_CSC_SIN_TABLE_NUM 61

typedef struct {
    hi_s16 csc_idc[ISP_CSC_DC_NUM];
    hi_s16 csc_odc[ISP_CSC_DC_NUM];
    hi_s16 csc_coef[ISP_CSC_COEF_NUM];
} isp_csc_attr;

typedef struct {
    hi_bool csc_usr_enable;
    hi_bool limited_range_enable;
    hi_bool ext_enable;
    hi_bool ctmode_enable;
    hi_color_gamut color_gamut;
    hi_u8   hue;
    hi_u8   contrast;
    hi_u8   luma;
    hi_u8   sat;
    isp_csc_attr isp_csc_attr_s;

    hi_bool update;
} isp_csc;

/* BT.709 -> RGB coefficient matrix */
static isp_csc_attr g_csc_attr_601 = {
    /* csc input DC component (IDC) */
    {0, 0, 0},
    /* csc output DC component (ODC) */
    {0, 128, 128},
    /* csc multiplier coefficient */
    {299, 587, 114, -172, -339, 511, 511, -428, -83},
};

/* BT.709 -> RGB coefficient matrix */
static isp_csc_attr g_csc_attr_601_limited = {
    /* csc input DC component (IDC) */
    {0, 0, 0},
    /* csc output DC component (ODC) */
    {16, 128, 128},
    /* csc multiplier coefficient */
    {258, 504, 98, -148, -291, 439, 439, -368, -71},
};

/* RGB -> YUV BT.709 coefficient matrix */
static isp_csc_attr g_csc_attr_709 = {
    /* csc input DC component (IDC) */
    {0, 0, 0},
    /* csc output DC component (ODC) */
    {0, 128, 128},
    /* csc multiplier coefficient */
    {213, 715, 72, -117, -394, 511, 511, -464, -47},
};

/* BT.709 -> RGB coefficient matrix */
static isp_csc_attr g_csc_attr_709_limited = {
    /* csc input DC component (IDC) */
    {0, 0, 0},
    /* csc output DC component (ODC) */
    {16, 128, 128},
    /* csc multiplier coefficient */
    {184, 614, 62, -101, -338, 439, 439, -399, -40},
};

static const int g_csc_sin_table[ISP_CSC_SIN_TABLE_NUM] = {
    -500,  -485,  -469,  -454,  -438,  -422,  -407,  -391,  -374,  -358,
    -342,  -325,  -309,  -292,  -276,  -259,  -242,  -225,  -208,  -191,
    -174,  -156,  -139,  -122,  -104,   -87,   -70,   -52,   -35,   -17,
    0,    17,    35,    52,    70,    87,   104,   122,   139,   156,
    174,   191,   208,   225,   242,   259,   276,   292,   309,   325,
    342,   358,   374,   391,   407,   422,   438,   454,   469,   485,
    500
};

static const int g_csc_cos_table[ISP_CSC_SIN_TABLE_NUM] = {
    866,   875,   883,   891,   899,   906,   914,   921,   927,   934,
    940,   946,   951,   956,   961,   966,   970,   974,   978,   982,
    985,   988,   990,   993,   995,   996,   998,   999,   999,  1000,
    1000,  1000,   999,   999,   998,   996,   995,   993,   990,   988,
    985,   982,   978,   974,   970,   966,   961,   956,   951,   946,
    940,   934,   927,   921,   914,   906,   899,   891,   883,   875,
    866
};

isp_csc      g_csc_ctx[ISP_MAX_PIPE_NUM];
isp_csc_attr g_csc_attr_ctx[ISP_MAX_PIPE_NUM];

#define csc_get_ctx(dev, ctx)            ctx = &g_csc_ctx[dev]
#define csc_attr_get_ctx(dev, attr_ctx)  attr_ctx = &g_csc_attr_ctx[dev]

/* CSC reg write interface */
static hi_void csc_dyna_regs_config(isp_csc_attr *csc_attr, isp_csc_dyna_cfg *dyna_reg_cfg)
{
    hi_u8 i;
    hi_u16 norm_coeff1 = 1024;
    hi_u16 norm_coeff2 = 1000;

    for (i = 0; i < ISP_CSC_COEF_NUM; i++) {
        dyna_reg_cfg->csc_coef[i] = csc_attr->csc_coef[i] * norm_coeff1 / norm_coeff2;
    }

    for (i = 0; i < ISP_CSC_DC_NUM; i++) {
        dyna_reg_cfg->csc_in_dc[i]  = csc_attr->csc_idc[i] * 4; // multiply by 4 bits to fit register
        dyna_reg_cfg->csc_out_dc[i] = csc_attr->csc_odc[i] * 4; // multiply by 4 bits to fit register
    }

    dyna_reg_cfg->resh = HI_TRUE;
}

static hi_void csc_regs_initialize(hi_vi_pipe vi_pipe, isp_reg_cfg *reg_cfg)
{
    // csc regs initialize
    hi_u8 i;
    isp_csc_attr  csc_attr;

    // initilize color gamut information
    memcpy_s(&csc_attr, sizeof(isp_csc_attr), &g_csc_attr_709, sizeof(isp_csc_attr));

    // config all four csc attributes.
    for (i = 0; i < reg_cfg->cfg_num; i++) {
        // coefficiets config
        csc_dyna_regs_config(&csc_attr, &reg_cfg->alg_reg_cfg[i].csc_cfg.dyna_reg_cfg);

        reg_cfg->alg_reg_cfg[i].csc_cfg.enable = HI_TRUE;
    }

    // refresh flags are forced on
    reg_cfg->cfg_key.bit1_csc_cfg = HI_TRUE;

    return;
}

static hi_void csc_ext_regs_initialize(hi_vi_pipe vi_pipe)
{
    hi_u8 i;

    isp_csc_attr  csc_attr;
    hi_ext_system_csc_gamuttype_write(vi_pipe, COLOR_GAMUT_BT709);
    memcpy_s(&csc_attr, sizeof(isp_csc_attr), &g_csc_attr_709, sizeof(isp_csc_attr));

    // write csc related parameters
    hi_ext_system_csc_hue_write(vi_pipe, ISP_CSC_DFT_HUE);
    hi_ext_system_csc_sat_write(vi_pipe, ISP_CSC_DFT_SAT);
    hi_ext_system_csc_contrast_write(vi_pipe, ISP_CSC_DFT_CON);
    hi_ext_system_csc_luma_write(vi_pipe, ISP_CSC_DFT_LUMA);
    hi_ext_system_csc_limitrange_en_write(vi_pipe, HI_FALSE);
    hi_ext_system_csc_ext_en_write(vi_pipe, HI_TRUE);
    hi_ext_system_csc_ctmode_en_write(vi_pipe, HI_TRUE);
    hi_ext_system_csc_attr_update_write(vi_pipe, HI_TRUE);

    // write DC components
    for (i = 0; i < ISP_CSC_DC_NUM; i++) {
        hi_ext_system_csc_dcin_write(vi_pipe, i,  csc_attr.csc_idc[i]);
        hi_ext_system_csc_dcout_write(vi_pipe, i, csc_attr.csc_odc[i]);
    }

    // write coefficients
    for (i = 0; i < ISP_CSC_COEF_NUM; i++) {
        hi_ext_system_csc_coef_write(vi_pipe, i, csc_attr.csc_coef[i]);
    }

    hi_ext_system_csc_enable_write(vi_pipe, HI_TRUE);

    return;
}

static hi_void csc_read_ext_regs(hi_vi_pipe vi_pipe, isp_csc *csc)
{
    hi_u8 i;
    hi_u8 gamut;

    csc->update = hi_ext_system_csc_attr_update_read(vi_pipe);
    hi_ext_system_csc_attr_update_write(vi_pipe, HI_FALSE);

    if (csc->update) {
        csc->limited_range_enable = hi_ext_system_csc_limitrange_en_read(vi_pipe);
        csc->ext_enable = hi_ext_system_csc_ext_en_read(vi_pipe);
        csc->ctmode_enable = hi_ext_system_csc_ctmode_en_read(vi_pipe);

        gamut = hi_ext_system_csc_gamuttype_read(vi_pipe);
        if (gamut == COLOR_GAMUT_BT2020) {
            isp_warn_trace("invalid color gamut type, set back to previous gamut type!\n");
        } else {
            csc->color_gamut = gamut;
        }

        csc->contrast = hi_ext_system_csc_contrast_read(vi_pipe);
        csc->hue = hi_ext_system_csc_hue_read(vi_pipe);
        csc->sat = hi_ext_system_csc_sat_read(vi_pipe);
        csc->luma = hi_ext_system_csc_luma_read(vi_pipe);

        for (i = 0; i < ISP_CSC_COEF_NUM; i++) {
            csc->isp_csc_attr_s.csc_coef[i] = hi_ext_system_csc_coef_read(vi_pipe, i);
        }

        // DC components read
        for (i = 0; i < ISP_CSC_DC_NUM; i++) {
            csc->isp_csc_attr_s.csc_idc[i] = hi_ext_system_csc_dcin_read(vi_pipe, i);
            csc->isp_csc_attr_s.csc_odc[i] = hi_ext_system_csc_dcout_read(vi_pipe, i);
        }
    }

    return;
}

hi_s32 csc_cal_matrix(hi_vi_pipe vi_pipe, isp_csc *csc, isp_csc_attr *csc_coef)
{
    hi_s32 luma     = 0;
    hi_s32 contrast, satu, hue;
    hi_s32 i = 0;
    hi_s16 black_in = 0;
    hi_s16 black_out = 0;
    hi_s16 norm_factor2 = 100;
    hi_s16 norm_factor3 = 1000;
    hi_s16 norm_factor4 = 10000;
    isp_csc_attr *csc_coef_tmp = HI_NULL;

    if (csc->ext_enable) {
        luma = (hi_s32)csc->luma - 50;  /* -50 ~ 50 */
        luma = luma * 0x80 / 50;  /* -128 ~ 128 */ // convert by 50
        luma = (luma == 128) ? 127 : luma; // max value shold be 127(7bits) , not 128(8bits)
    } else {
        luma = (hi_s32)csc->luma * 64 / 100 - 32; // convert from [0, 100] to [-32, 32], use 64 as norm
        luma = CLIP3(luma, -128, 128); /* limit the result of luma, ,[-128, 128] */
    }

    contrast = ((hi_s32)csc->contrast - 50) * 2 + 100; // convert contrast from [0, 100] to [0, 200], use 50 to convert
    hue      = (hi_s32)csc->hue * 60 / 100; // convert hue from [0, 100] to [0, 60]
    hue      = CLIP3(hue, 0, 60); // hue range in [0, 60]
    satu     = ((hi_s32)csc->sat - 50) * 2 + 100; // convert satu from [0, 100] to [0, 200], use 50 to convert

    switch (csc->color_gamut) {
        case COLOR_GAMUT_BT601:
            csc_coef_tmp  = (csc->limited_range_enable) ? &g_csc_attr_601_limited : &g_csc_attr_601;
            break;
        case COLOR_GAMUT_BT709:
            csc_coef_tmp  = (csc->limited_range_enable) ? &g_csc_attr_709_limited : &g_csc_attr_709;
            break;
        case COLOR_GAMUT_USER:
            csc_coef_tmp  = &(csc->isp_csc_attr_s);
            csc_coef_tmp->csc_odc[0] = (csc->limited_range_enable) ? 16 : 0; // 16 as DC value
            break;
        default:
            isp_err_trace("invalid color gamut type!\n");
            return HI_FAILURE;
    }

    if ((csc->ctmode_enable) && (csc->color_gamut <= COLOR_GAMUT_USER)) {
        black_in  = -128; // -128 as default input dc value
        /* 110 = (128 * 219) / 256, full range to limited range conversion */
        black_out = (csc->limited_range_enable) ? 110 : 127; // 110 is get from (129*219)/256, 127 is default dc value
    } else {
        black_in  = 0;
        black_out = 0;
    }

    csc_coef->csc_coef[0] = (hi_s16)((contrast * ((hi_s32)csc_coef_tmp->csc_coef[0])) / norm_factor2);
    csc_coef->csc_coef[1] = (hi_s16)((contrast * ((hi_s32)csc_coef_tmp->csc_coef[1])) / norm_factor2);
    csc_coef->csc_coef[2] = (hi_s16)((contrast * ((hi_s32)csc_coef_tmp->csc_coef[2])) / norm_factor2); // [2]
    csc_coef->csc_coef[3] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[3] * g_csc_cos_table[hue] // 3
                            + csc_coef_tmp->csc_coef[6] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 6
    csc_coef->csc_coef[4] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[4] * g_csc_cos_table[hue] // 4
                            + csc_coef_tmp->csc_coef[7] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 7
    csc_coef->csc_coef[5] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[5] * g_csc_cos_table[hue] // 5
                            + csc_coef_tmp->csc_coef[8] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 8
    csc_coef->csc_coef[6] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[6] * g_csc_cos_table[hue] // 6
                            - csc_coef_tmp->csc_coef[3] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 3
    csc_coef->csc_coef[7] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[7] * g_csc_cos_table[hue] // 7
                            - csc_coef_tmp->csc_coef[4] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 4
    csc_coef->csc_coef[8] = (hi_s16)((contrast * satu * ((hi_s32)(csc_coef_tmp->csc_coef[8] * g_csc_cos_table[hue] // 8
                            - csc_coef_tmp->csc_coef[5] * g_csc_sin_table[hue]) / norm_factor3)) / norm_factor4);  // 5

    for (i = 0; i < ISP_CSC_DC_NUM; i++) {
        if (csc->color_gamut == COLOR_GAMUT_USER) {
            csc_coef_tmp->csc_idc[i] = 0;
        }
        csc_coef->csc_idc[i] = csc_coef_tmp->csc_idc[i] + black_in;
        csc_coef->csc_odc[i] = csc_coef_tmp->csc_odc[i];
    }

    // add luma
    csc_coef->csc_odc[0] += ((hi_s16)luma + black_out);

    // update ext regs:
    for (i = 0; i < ISP_CSC_DC_NUM; i++) {
        hi_ext_system_csc_dcin_write(vi_pipe, i,  csc_coef->csc_idc[i]);
        hi_ext_system_csc_dcout_write(vi_pipe, i, csc_coef->csc_odc[i]);
    }

    if (csc->color_gamut != COLOR_GAMUT_USER) {
        for (i = 0; i < ISP_CSC_COEF_NUM; i++) {
            hi_ext_system_csc_coef_write(vi_pipe, i, csc_coef->csc_coef[i]);
        }
    }

    return HI_SUCCESS;
}

static hi_bool __inline check_csc_open(isp_csc *csc)
{
    return (csc->csc_usr_enable == HI_TRUE);
}

hi_s32 csc_proc_write(hi_vi_pipe vi_pipe, hi_isp_ctrl_proc_write *proc)
{
    return HI_SUCCESS;
}

hi_s32 csc_color_gamutinfo_get(hi_vi_pipe vi_pipe,  hi_isp_colorgammut_info *color_gamut_info)
{
    isp_csc    *csc = HI_NULL;

    csc_get_ctx(vi_pipe, csc);

    color_gamut_info->color_gamut = csc->color_gamut;

    return HI_SUCCESS;
}

hi_s32 isp_csc_init(hi_vi_pipe vi_pipe, hi_void *reg_cfg)
{
    isp_reg_cfg *local_reg_cfg = (isp_reg_cfg *)reg_cfg;

    csc_regs_initialize(vi_pipe, local_reg_cfg);
    csc_ext_regs_initialize(vi_pipe);

    return HI_SUCCESS;
}

hi_s32 isp_csc_run(hi_vi_pipe vi_pipe, const hi_void *stat_info,
                   hi_void *reg_cfg, hi_s32 rsv)
{
    hi_u16 i;
    hi_s32 ret;

    isp_csc_attr *csc_attr = HI_NULL;
    isp_csc      *csc      = HI_NULL;
    isp_usr_ctx  *isp_ctx  = HI_NULL;

    isp_get_ctx(vi_pipe, isp_ctx);
    csc_get_ctx(vi_pipe, csc);
    csc_attr_get_ctx(vi_pipe, csc_attr);

    isp_reg_cfg *local_reg_cfg = (isp_reg_cfg *)reg_cfg;

    if (isp_ctx->linkage.defect_pixel) {
        return HI_SUCCESS;
    }

    csc->csc_usr_enable = hi_ext_system_csc_enable_read(vi_pipe);

    for (i = 0; i < local_reg_cfg->cfg_num; i++) {
        local_reg_cfg->alg_reg_cfg[i].csc_cfg.enable = csc->csc_usr_enable;
    }

    local_reg_cfg->cfg_key.bit1_csc_cfg = 1;

    /* check hardware setting */
    if (!check_csc_open(csc)) {
        return HI_SUCCESS;
    }

    // read ext regs
    csc_read_ext_regs(vi_pipe, csc);

    // calculate actual csc coefs
    ret = csc_cal_matrix(vi_pipe, csc, csc_attr);
    if (ret == HI_FAILURE) {
        return HI_FAILURE;
    }

    for (i = 0; i < local_reg_cfg->cfg_num; i++) {
        // coefficiets config
        csc_dyna_regs_config(csc_attr, &local_reg_cfg->alg_reg_cfg[i].csc_cfg.dyna_reg_cfg);
    }

    // refresh flags are forced on
    local_reg_cfg->cfg_key.bit1_csc_cfg = HI_TRUE;

    return HI_SUCCESS;
}

hi_s32 isp_csc_ctrl(hi_vi_pipe vi_pipe, hi_u32 cmd, hi_void *value)
{
    switch (cmd) {
        case ISP_PROC_WRITE:
            csc_proc_write(vi_pipe, (hi_isp_ctrl_proc_write *)value);
            break;
        case ISP_COLORGAMUTINFO_GET:
            csc_color_gamutinfo_get(vi_pipe, (hi_isp_colorgammut_info *)value);
            break;

        default:
            break;
    }

    return HI_SUCCESS;
}

hi_s32 isp_csc_exit(hi_vi_pipe vi_pipe)
{
    return HI_SUCCESS;
}

hi_s32 isp_alg_register_csc(hi_vi_pipe vi_pipe)
{
    isp_usr_ctx *isp_ctx = HI_NULL;
    isp_alg_node *algs = HI_NULL;

    isp_get_ctx(vi_pipe, isp_ctx);
    isp_alg_check(isp_ctx->alg_key.bit1_csc);
    algs = isp_search_alg(isp_ctx->algs);
    isp_check_pointer(algs);

    algs->alg_type = ISP_ALG_CSC;
    algs->alg_func.pfn_alg_init = isp_csc_init;
    algs->alg_func.pfn_alg_run  = isp_csc_run;
    algs->alg_func.pfn_alg_ctrl = isp_csc_ctrl;
    algs->alg_func.pfn_alg_exit = isp_csc_exit;
    algs->used = HI_TRUE;

    return HI_SUCCESS;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* end of #ifdef __cplusplus */
